#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/mman.h>
#include <stdint.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <iostream>
#include <thread>

#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>

using namespace boost::posix_time;
using namespace boost::gregorian;

enum {
	USE_NONE,
	USE_O_SYNC,
	USE_MMAP,
	USE_FSYNC,
	USE_FDATASYNC
};

int main( int argc, char *argv[] )
{
	int opt = USE_NONE;
	const char *opt_str =
		"o" // o_sync
		"m" // mmap
		"f" // fsync
		"d" // fdatasync
		"h"; // help

	int op;
	while( (op = getopt( argc, argv, opt_str)) != -1 ) {
		switch( op ) {
		case 'o':
			opt = USE_O_SYNC;
			break;
		case 'm':
			opt = USE_MMAP;
			break;
		case 'f':
			opt = USE_FSYNC;
			break;
		case 'd':
			opt = USE_FDATASYNC;
			break;
		case 'h':
		default:
			std::cerr << argv[0] << "[ -omfdh ] filename" << std::endl;
			exit(EXIT_FAILURE);
		}
	}

	const char *filename = argv[optind];
	int fd = open( filename, O_CREAT | O_RDWR | (USE_O_SYNC == opt ? O_SYNC : 0 ), 0644 );
	if( fd < 0 ) {
		std::cerr << "open " << filename << " error, errno : " << errno << std::endl;
		exit(EXIT_FAILURE);
	}

	const off_t len = 4 * 1024; // 4KB, page size
	ftruncate( fd, len );

	void *mmaped = NULL;
	if( opt == USE_MMAP ) {
		mmaped = mmap(0, len, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE, fd, 0);
		if( NULL == mmaped ) {
			std::cerr << "mmaped failed, errno: " << errno << std::endl;
			exit(EXIT_FAILURE);
		}
	}

	const int buf_size = 1024;
	char buf[buf_size];

	volatile uint64_t counter = 0;
	std::thread run_thread(
			[&counter, fd, opt, mmaped, len, buf] {
				while(true) {
					if( opt == USE_MMAP ) {
						memcpy( (char *)mmaped, buf, buf_size);
						msync( mmaped, len, MS_SYNC );
					} else {
						pwrite( fd, buf, buf_size, 0 );
						switch( opt ) {
						case USE_FSYNC:
							fsync( fd );
							break;
						case USE_FDATASYNC:
							fdatasync( fd );
							break;
						}
					}

					++counter;
				}
			}
			);

	std::thread statistics_thread(
			[&counter]{
			while(true) {
				auto begin = microsec_clock::universal_time();
				auto begin_count = counter;
				sleep(1);
				auto dur = microsec_clock::universal_time() - begin;
				auto end_count = counter;
				std::cout << "OPT COUNT: " << (end_count - begin_count)
					<< " TOTAL COUNT: " << end_count
					<< " TPS: " << ((double)(end_count - begin_count)) / (double)dur.total_milliseconds() * 1000
					<< std::endl;
				// std::cout << dur.total_milliseconds() << std::endl;
			}
			}
			);

	run_thread.join();
	statistics_thread.join();
}

